/*
 *   This File is Part of CuteOgre Game Engine
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *	Copyright 2010 Alexandre Pires ( c.alexandre.pires@gmail.com )
 *  Created on: 2010/08/22
 */

#include "SoundSource.h"
#include "Ogg/OggFile.h"
#include "Ogg/VorbisStream.h"

SoundSource::SoundSource(ALuint rNumBuffers, OggFilePtr rFile) {

	if (rFile.isNull())
		throw errorString(OV_EREAD);

	if (rFile->getVorbisStream().isNull())
		throw errorString(OV_ENOTVORBIS);

	mOggFile = rFile;

	mNumBuffers = rNumBuffers;

	mBuffers = OpenAlBuffers(new ALuint[rNumBuffers]);

	alGenBuffers(rNumBuffers, mBuffers.data());
	alGenSources(1, &mSource);

	checkFormat();
}

SoundSource::~SoundSource() {
	release();
}

bool SoundSource::startPlayback() {

	if (isPlaying())
		return true;

	for (ALuint i = 0; i < mNumBuffers; i++) {
		if (!stream(mBuffers.data()[i]))
			return false;
	}

	alSourceQueueBuffers(mSource, mNumBuffers, mBuffers.data());

	alSourcePlay(mSource);

	return true;
}

bool SoundSource::stopPlayback() {
	alSourceStop(mSource);
	return check();
}

bool SoundSource::isPlaying() {
	ALenum state;
	alGetSourcei(mSource, AL_SOURCE_STATE, &state);
	return (state == AL_PLAYING);
}

float SoundSource::getPitch() {
	ALfloat value;
	alGetSourcef(mSource, AL_PITCH, &value);
	return value;
}

float SoundSource::getGain() {
	ALfloat value;
	alGetSourcef(mSource, AL_GAIN, &value);
	return value;
}

float SoundSource::getMaxDistance() {
	ALfloat value;
	alGetSourcef(mSource, AL_MAX_DISTANCE, &value);
	return value;
}

float SoundSource::getRollOffFactor() {
	ALfloat value;
	alGetSourcef(mSource, AL_ROLLOFF_FACTOR, &value);
	return value;
}

float SoundSource::getMaxGain() {
	ALfloat value;
	alGetSourcef(mSource, AL_MAX_GAIN, &value);
	return value;
}

float SoundSource::getConeOuterGain() {
	ALfloat value;
	alGetSourcef(mSource, AL_CONE_OUTER_GAIN, &value);
	return value;
}

float SoundSource::getConeInnerAngle() {
	ALfloat value;
	alGetSourcef(mSource, AL_CONE_INNER_ANGLE, &value);
	return value;
}

float SoundSource::getConeOuterAngle() {
	ALfloat value;
	alGetSourcef(mSource, AL_CONE_OUTER_ANGLE, &value);
	return value;
}

QVector3D SoundSource::getPosition() {
	ALfloat value[3];
	alGetSource3f(mSource, AL_CONE_INNER_ANGLE, &value[0], &value[1], &value[2]);
	return QVector3D(value[0], value[1], value[2]);
}

QVector3D SoundSource::getVelocity() {
	ALfloat value[3];
	alGetSource3f(mSource, AL_CONE_INNER_ANGLE, &value[0], &value[1], &value[2]);
	return QVector3D(value[0], value[1], value[2]);
}

QVector3D SoundSource::getDirection() {
	ALfloat value[3];
	alGetSource3f(mSource, AL_CONE_INNER_ANGLE, &value[0], &value[1], &value[2]);
	return QVector3D(value[0], value[1], value[2]);
}

bool SoundSource::isSourceRelative() {
	ALint value;
	alGetSourcei(mSource, AL_SOURCE_RELATIVE, &value);
	return bool(value);
}

SoundSource::SourceType SoundSource::getSourceType() {
	ALenum value;
	alGetSourcei(mSource, AL_SOURCE_TYPE, &value);
	return SourceType(value);
}

bool SoundSource::isLoop() {
	ALint value;
	alGetSourcei(mSource, AL_LOOPING, &value);
	return bool(value);
}

bool SoundSource::setPitch(float rPitch) {
	alSourcef(mSource, AL_PITCH, rPitch);
	return check();
}

bool SoundSource::setGain(float rGain) {
	alSourcef(mSource, AL_GAIN, rGain);
	return check();
}

bool SoundSource::setMaxDistance(float rDistance) {
	alSourcef(mSource, AL_MAX_DISTANCE, rDistance);
	return check();
}

bool SoundSource::setRollOffFactor(float rRollOffFactor) {
	alSourcef(mSource, AL_ROLLOFF_FACTOR, rRollOffFactor);
	return check();
}

bool SoundSource::setMaxGain(float rMaxGain) {
	alSourcef(mSource, AL_MAX_GAIN, rMaxGain);
	return check();
}

bool SoundSource::setConeOuterGain(float rOuterGain) {
	alSourcef(mSource, AL_CONE_OUTER_GAIN, rOuterGain);
	return check();
}

bool SoundSource::setConeInnerAngle(float rAngle) {
	alSourcef(mSource, AL_CONE_INNER_ANGLE, rAngle);
	return check();
}

bool SoundSource::setConeOuterAngle(float rAngle) {
	alSourcef(mSource, AL_CONE_OUTER_ANGLE, rAngle);
	return check();
}

bool SoundSource::setPosition(float rX, float rY, float rZ) {
	alSource3f(mSource, AL_POSITION, rX, rY, rZ);
	return check();
}

bool SoundSource::setPosition(const QVector3D& rVector) {
	return setPosition(rVector.x(), rVector.y(), rVector.z());
}

bool SoundSource::setVelocity(float rX, float rY, float rZ) {
	alSource3f(mSource, AL_VELOCITY, rX, rY, rZ);
	return check();
}

bool SoundSource::setVelocity(const QVector3D& rVector) {
	return setVelocity(rVector.x(), rVector.y(), rVector.z());
}

bool SoundSource::setDirection(float rX, float rY, float rZ) {
	alSource3f(mSource, AL_DIRECTION, rX, rY, rZ);
	return check();
}

bool SoundSource::setDirection(const QVector3D& rVector) {
	return setDirection(rVector.x(), rVector.y(), rVector.z());
}

bool SoundSource::setSourceRelative(bool rOnOff) {
	alSourcef(mSource, AL_SOURCE_RELATIVE, int(rOnOff));
	return check();
}

bool SoundSource::setSourceType(SourceType rType) {
	alSourcef(mSource, AL_SOURCE_TYPE, rType);
	return check();
}

bool SoundSource::setLoop(bool rOnOff) {
	alSourcei(mSource, AL_SOURCE_RELATIVE, int(rOnOff));
	return check();
}

void SoundSource::release() {

	empty();

	alDeleteSources(1, &mSource);
	alDeleteBuffers(mNumBuffers, mBuffers.data());

	mOggFile.clear();
}

void SoundSource::empty() {
	int queued;

	alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued);

	while (queued--) {
		ALuint buffer;

		alSourceUnqueueBuffers(mSource, 1, &buffer);
	}
}

bool SoundSource::update() {
	int processed;
	bool active = true;

	alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed);

	while (processed--) {
		ALuint buffer;
		alSourceUnqueueBuffers(mSource, 1, &buffer);
		active = stream(buffer);
		alSourceQueueBuffers(mSource, 1, &buffer);
	}

	return active;
}

bool SoundSource::stream(ALuint rBuffer) {

	char pcm[mBufferSize];
	int size = 0;
	int result;
	bool eof = false;

	while (size < mBufferSize) {
		result = mOggFile->getVorbisStream()->read(pcm + size, mBufferSize
				- size, 0, 2, 1);

		if (result > 0)
			size += result;
		else if (result == OV_EOF) {
			eof = true;
			break;
		} else if (result < 0)
			throw errorString(result);
	}

	if (size == 0)
		return false;

	alBufferData(rBuffer, mFormat, pcm, size,
			mOggFile->getVorbisStream()->getRate());
	check();

	return !eof;
}

void SoundSource::checkFormat() {
	switch (mOggFile->getVorbisStream()->getChannels()) {
	case 1:
		mFormat = AL_FORMAT_MONO16;
		mBufferSize = mOggFile->getVorbisStream()->getRate() >> 1;
		mBufferSize -= (mBufferSize % 2);
		break;
	case 2:
		mFormat = AL_FORMAT_STEREO16;
		mBufferSize = mOggFile->getVorbisStream()->getRate();
		mBufferSize -= (mBufferSize % 4);
		break;
	case 4:
		mFormat = alGetEnumValue("AL_FORMAT_QUAD16");
		mBufferSize = mOggFile->getVorbisStream()->getRate() * 2;
		mBufferSize -= (mBufferSize % 8);
		break;
	case 6:
		mFormat = alGetEnumValue("AL_FORMAT_51CHN16");
		mBufferSize = mOggFile->getVorbisStream()->getRate() * 3;
		mBufferSize -= (mBufferSize % 12);
		break;
	case 7:
		mFormat = alGetEnumValue("AL_FORMAT_61CHN16");
		mBufferSize = mOggFile->getVorbisStream()->getRate() * 4;
		mBufferSize -= (mBufferSize % 16);
		break;
	case 8:
		mFormat = alGetEnumValue("AL_FORMAT_71CHN16");
		mBufferSize = mOggFile->getVorbisStream()->getRate() * 5;
		mBufferSize -= (mBufferSize % 20);
		break;
	default:
		mFormat = AL_FORMAT_MONO16;
		mBufferSize = mOggFile->getVorbisStream()->getRate() >> 1;
		mBufferSize -= (mBufferSize % 2);
		break;
	}
}

QString SoundSource::errorString(int code) {
	switch (code) {
	case OV_EREAD:
		return QString("Read from media.");
	case OV_ENOTVORBIS:
		return QString("Not Vorbis data.");
	case OV_EVERSION:
		return QString("Vorbis version mismatch.");
	case OV_EBADHEADER:
		return QString("Invalid Vorbis header.");
	case OV_EFAULT:
		return QString("Internal logic fault (bug or heap/stack corruption.");
	default:
		return QString("Unknown Ogg error.");
	}
}
